# NOTE when you do this PLY adds a module name self. to all tokens
# not sure if this is an unexpected feature or not. It can be worked around
# but for now direct import seems the safest
##  import pysces.lib.lex as lex
##  import pysces.lib.yacc as yacc
import lex
import yacc
        
        

class MyInfixLexer:
    """
    Simple infix lexer based on the PLY documentation example and
    the PySCeS parser.
    """
    
    debug = 0
    
    NumpyFuncs = ['log', 'log2', 'log10', 'fabs', 'exp','floor', 'ceil', 'sqrt',\
                          'cos', 'cosh', 'sin', 'sinh', 'tan', 'tanh',\
                          'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctanh']
    
    PythonFuncs = ['pow','abs']

    UnsupportedFuncs = ['atan2', 'factorial', 'fmod',\
                       'frexp', 'hypot', 'ldexp', 'modf' ]


    FuncNames = tuple(NumpyFuncs + PythonFuncs + UnsupportedFuncs)

    precedence = (
        ('left',  'PLUS', 'MINUS'),
        ('left',  'TIMES', 'DIVIDE'),
        ('left',  'POWER'),
        ('right', 'UMINUS')
        )

    # List of token names
    tokens = ('REAL',
              'INT',
              'PLUS',
              'MINUS',
              'TIMES',
              'DIVIDE',
              'POWER',
              'LPAREN',
              'RPAREN',
              'NAME',
              'COMMA')

    def __init__(self):
        self.Int = r'\d+'                                      # Integer
        self.Dec = self.Int + '\.' + self.Int                            # Decimal
        self.Exp = r'([E|e][\+|\-]?)' + self.Int                    # Exponent
        self.Real = self.Dec  + '(' + self.Exp + ')?' + '|' + self.Int + self.Exp  # Real - dec w/o optional exp or int with exp

        # Simple tokens
        self.t_REAL = self.Real
        self.t_INT = self.Int
        self.t_PLUS = r'\+'
        self.t_MINUS = r'-'
        self.t_TIMES = r'\*'
        self.t_DIVIDE = r'/'
        self.t_POWER = '\*\*'
        self.t_LPAREN = r'\('
        self.t_RPAREN = r'\)'
        self.t_COMMA = r','


    def t_NAME(self,t):
        r'[a-zA-Z_][\w]*'
                            
        t.type = 'NAME'
        if self.debug: print '\tNAME: t.value', t.value
        return t

    # Define a rule so we can track line numbers
    def t_newline(self,t):
        r'\n+'
        t.lexer.lineno += len(t.value)

    # A string containing ignored characters (spaces and tabs)
    t_ignore  = ' \t'

    # Error handling rule
    def t_error(self,t):
        print "Illegal character '%s'" % t.value[0]
        t.lexer.skip(1)

    # Build the lexer
    def buildlexer(self,**kwargs):
        ##  reload(pysces.lib.lex)
        self.lexer = lex.lex(object=self, **kwargs)
    
    # Test it output
    def testlexer(self,data):
        self.lexer.input(data)
        while 1:
             tok = self.lexer.token()
             if not tok: break
             print tok


class MyInfixParser(MyInfixLexer):
    ParseOK = 1
    ModelUsesNumpyFuncs = 0
    names = None
    output = None
    input = None
    name_prefix = '<pre>'
    name_suffix = '<suf>'
    
    def __init__(self):
        MyInfixLexer.__init__(self)
        self.ParseErrors = []
        self.names = []

    def setNameStr(self, prefix, suffix):
        self.name_prefix = str(prefix)
        self.name_suffix = str(suffix)

    def p_error(self,t):
        self.ParseErrors.append(('Syntax error ', t.lineno, t.value, t.type))
        if self.debug: print 'Syntax error, Line ' + str(t.lineno) + ' : ' + str(t.value)
        tok = yacc.token()
        while tok and tok.type != 'REACTION_ID':
            tok = yacc.token()
        self.ParseOK = 0
        return tok

    def p_infix(self,t):
        '''Expression : Expression PLUS Expression
                      | Expression MINUS Expression
                      | Expression TIMES Expression
                      | Expression DIVIDE Expression
                      | Power
                      | Number
                      | Func'''
                    # |UMINUS : add if the
                    #  alternative for p_uminus is used    
                    
        if len(t.slice)==4:
            if self.debug: print '\t', t[1], t[2], t[3]
            t[0] = t[1] + t[2] + t[3]
        else:
            if self.debug: print '\t', t[0], t[1]
            t[0] = t[1]

    def p_power(self,t):
        '''Power : Expression POWER Expression'''
    
        ##  t[0] = 'pow('+ t[1] + ',' + t[3] + ')' #changed to make it DeriVar compatible
        t[0] = "%s**%s" % (t[1], t[3])
    
   
    def p_number(self, t):
        '''Number : REAL
                  | INT
                  | NAME'''
        try:
            tx = float(t[1])
            t[0] = t[1]
        except ValueError:
            self.names.append(t[1])
            t[0] = self.name_prefix + t[1] + self.name_suffix
            
    def p_uminus(self,t):
        '''Expression : MINUS Expression %prec UMINUS'''
        # Alternative '''UMINUS : MINUS Expression'''

        t[0] = t[1] + t[2]

    def p_function(self,t):
        '''Func : LPAREN ArgList RPAREN
                | NAME LPAREN ArgList RPAREN'''

        if self.debug: print t[0], t[1] , t[2], t[3], t[4]
        if t[1] in self.NumpyFuncs:
            try:
                t[0] = 'numpy.' + t[1] + t[2] + t[3] + t[4]
            except Exception, EX:
                print 'Function Parse error 1 (please report!)\n', EX
            self.ModelUsesNumpyFuncs = True
        elif t[1] in self.PythonFuncs:
            try:
                t[0] = t[1] + t[2] + t[3] + t[4]
            except Exception, EX:
                print 'Function Parse error 2 (please report!)\n', EX
        else:
            t[0] = t[1] + t[2] + t[3]

    def p_arglist(self,t):
        '''ArgList : Expression
                   | Expression COMMA Expression'''

        if self.debug: print len(t)
        try:
            if len(t) == 2:
                if self.debug: print t[1]
                t[0] = t[1]
            elif len(t) == 4:
                if self.debug: print t[1], t[2], t[3]
                t[0] = t[1] + t[2] + t[3]
                
            if self.debug: print t[0]
        except Exception, EX:
            print 'Function ArgList error (please report!)\n', EX


    def buildparser(self, **kwargs):
        ##  reload(pysces.lib.yacc)
        self.parser = yacc.yacc(module=self, **kwargs)

    def parse(self, data):
        self.ParseErrors = []
        self.names = []        
        self.input = data
        self.output = self.parser.parse(data)


##  def postfix(infix):
    ##  """
    ##  Many thanks to James Dominy who knows lots more about this than I do :-)
    ##  """
    ##  operator_stack = []
    ##  output_queue = ''

    ##  for c in infix:
        ##  if c.isdigit():
            ##  output_queue += c
        ##  elif c in ['+', '-', '*', '/']:
            ##  while len(operator_stack) > 0 and operator_stack[len(operator_stack)-1] in ['*', '/'] and c in ['+', '-']:
                ##  output_queue += operator_stack.pop()
            ##  operator_stack.append(c)
        ##  elif c == '(':
            ##  operator_stack.append(c)
        ##  elif c == ')':
            ##  o = operator_stack.pop()
            ##  while o != '(':
                ##  output_queue += o
                ##  o = operator_stack.pop()
    ##  while len(operator_stack) > 0:
        ##  output_queue += operator_stack.pop()
    
    ##  return output_queue

